

#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

/*** defines ***/

#define CTRL_KEY(k) ((k)&0x1f)

#define KILO_VERSION "0.0.1"
#define KILO_TAB_STOP 8
#define KILO_QUIT_TIMES 3

enum editorKey {
  BACKSPACE = 127,
  ARROW_LEFT = 1000,
  ARROW_RIGHT,
  ARROW_UP,
  ARROW_DOWN,
  DEL_KEY,
  HOME_KEY,
  END_KEY,
  PAGE_UP,
  PAGE_DOWN
};

enum editorHighlight {
  HL_NORMAL = 0,
  HL_NUMBER,
  HL_MATCH,
  HL_STRING,
  HL_COMMENT,
  HL_MLCOMMENT,
  HL_KEYWORD1,
  HL_KEYWORD2
};

#define HL_HIGHLIGHT_NUMBERS (1 << 0)
#define HL_HIGHLIGHT_STRINGS (1 << 1)
/*** data ***/

struct editorSyntax {
  char *filetype;//存储文件类型，如c、python、java等
  char **filematch;//该成员是一个指向字符串数组的指针，包含与此语法类型相关的文件扩展名（如 ".c"、".h"、".py" 等）
  char **keywords;//字符串数组的指针，列出该语言的关键字（如 if、for、return 等）
  char *singleline_comment_start;//存储单行注释的起始符号，例如 C 语言中的 //
  char *multiline_comment_start;//存储多行注释的起始符号，例如 C 语言中的 /*。用于标识多行注释的开始
  char *multiline_comment_end;//存储多行注释的结束符号，例如 C 语言中的 */
  int flags;//该成员用于指定语法高亮的其他相关选项的标志。例如，可以使用位运算表明是否启用数字和字符串的高亮（HL_HIGHLIGHT_NUMBERS 和 HL_HIGHLIGHT_STRINGS）。
};

typedef struct erow {//存储某一行数据的信息
  int idx;//该行在整个文本中的行号
  int size;//表示该行中字符的实际数量，包括所有的字符和空白
  int rsize; //表示该行在屏幕上渲染后的字符数量。这通常与 size 相同，
  //但如果存在制表符（\t），则 rsize 可能会大于 size。因为在渲染时，制表符可能会被转换成多个空格字符
  char *chars;//指向一个字符数组（字符串），存储该行的实际文本内容。它保存了用户在该行输入的所有字符
  char *render;//指向一个字符数组，存储经过渲染后的文本内容。这个内容可能显示为不同于原始输入的样式，比如处理了制表符的情况
  unsigned char *hl;//指向一个无符号字符数组，用于存储该行中每个字符的高亮信息。每个字符都有一个对应的高亮类别（如关键字、注释等），这有助于在渲染时应用合适的颜色。
  //这个数组的长度和rsize相同
  int hl_open_comment;//表示该行是否处于打开的多行注释状态（如 /* 注释未结束）。如果这一行的注释是多行注释的一部分，这个标志将被用来区分何时结束注释
} erow;
//存储编辑器的配置信息
struct editorConfig {
  int cx, cy;//光标位置
  int rx;//表示光标在终端显示中的位置（考虑了制表符的宽度等因素），用于确定光标的实际显示位置。
  int rowoff;//当前屏幕中显示的最上面一行在整个文本中的行号
  int coloff;//当前屏幕中显示的最左边一列在整个文本中的列号
  int screenrows;//表示当前终端窗口能够显示的行数（减去状态栏的行数）
  int screencols;//表示当前终端窗口能够显示的列数
  int numrows;//表示文本中总行数
  erow *row;//一个指向 erow 结构体数组的指针，存储每一行的数据（如文本内容、渲染内容和语法高亮信息
  int dirty;//指示当前编辑器中是否有未保存的更改。通常，非零值表示有更改。
  char *filename;//当前打开的文件名
  char statusmsg[80];//存储状态栏显示的信息
  time_t statusmsg_time;//long类型，记录上一次状态栏信息更新的时间
  struct editorSyntax *syntax;//存储当前文件的语法高亮配置信息，包括支持的文件类型、关键字等
  struct termios orig_termios;//终端的原始属性，用于恢复终端设置
};

struct editorConfig E;


char *C_HL_extensions[] = {".c", ".h", ".cpp", NULL};
char *C_HL_keywords[] = {"switch",    "if",      "while",   "for",    "break",
                         "continue",  "return",  "else",    "struct", "union",
                         "typedef",   "static",  "enum",    "class",  "case",
                         "int|",      "long|",   "double|", "float|", "char|",
                         "unsigned|", "signed|", "void|",   NULL};

struct editorSyntax HLDB[] = {//初始化c语言的语法高亮配置信息
    {"c", C_HL_extensions, C_HL_keywords, "//", "/*", "*/",
     HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS},
};

#define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))//获取数组中元素的数量


void editorSetStatusMessage(const char *fmt, ...);
void editorRefreshScreen();
char *editorPrompt(char *prompt, void (*callback)(char *, int));

/*** terminal ***/

void die(const char *s) {
  write(STDOUT_FILENO, "\x1b[2J", 4);
  write(STDOUT_FILENO, "\x1b[H", 3);
  perror(s);
  exit(1);
}

void disableRawMode() {
  //修改终端设置，将终端恢复为原始属性
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) {
    die("tcsetattr");
  }
}

void enableRawMode() {
  //使用系统调用，获取终端的各项参数将其保存到termios结构体中
  if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) {
    die("tcgetattr");
  }
  //程序退出时会自动调用传给atexit的函数
  atexit(disableRawMode);
   //配置各项参数，将终端设置为原始模式
  struct termios raw = E.orig_termios;
  raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP | IXON | ICRNL);
  raw.c_oflag &= ~(OPOST);
  raw.c_cflag |= (CS8);
  raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
  raw.c_cc[VMIN] = 0;
  raw.c_cc[VTIME] = 1;
  //应用新参数
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
    die("tcsetattr");
  }
}

int editorReadKey() {
  int nread;
  char c;
  while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
    if (nread == -1 && errno != EAGAIN)
      die("read");
  }

  if (c == '\x1b') {
    char seq[3];

    if (read(STDIN_FILENO, &seq[0], 1) != 1)
      return '\x1b';
    if (read(STDIN_FILENO, &seq[1], 1) != 1)
      return '\x1b';

    // up arrow key -> \x1b [ A
    // down arrow key -> \x1b [ B
    if (seq[0] == '[') {
      if (seq[1] >= '0' && seq[1] <= '9') {
        if (read(STDIN_FILENO, &seq[2], 1) != 1)
          return '\x1b';
        if (seq[2] == '~') {
          switch (seq[1]) {
          case '1':
            return HOME_KEY;
          case '3':
            return DEL_KEY;
          case '4':
            return END_KEY;
          case '5':
            return PAGE_UP;
          case '6':
            return PAGE_DOWN;
          case '7':
            return HOME_KEY;
          case '8':
            return END_KEY;
          }
        }
      } else {
        switch (seq[1]) {
        case 'A':
          return ARROW_UP;
        case 'B':
          return ARROW_DOWN;
        case 'C':
          return ARROW_RIGHT;
        case 'D':
          return ARROW_LEFT;
        case 'H':
          return HOME_KEY;
        case 'F':
          return END_KEY;
        }
      }
    } else if (seq[0] == 'O') {
      switch (seq[1]) {
      case 'H':
        return HOME_KEY;
      case 'F':
        return END_KEY;
      }
    }
    return '\x1b';
  } else {
    return c;
  }
}
//获取当前光标所在的位置
int getCursorPosition(int *rows, int *cols) {
  char buf[32];//用于接收终端的响应
  unsigned int i = 0;
  if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4)//发送这个序列，请求终端返回当前光标位置
    return -1;

  while (i < sizeof(buf) - 1) {
    if (read(STDIN_FILENO, &buf[i], 1) != 1)
      break;
    if (buf[i] == 'R')
      break;
    i++;
//通过 read() 从标准输入流中读取数据，直到接收到完整的响应。每次读取一个字符，直到达到缓冲区的大小，或读取到字符 R（表示光标位置返回结束）。
//i 用于追踪存储在 buf 中的字符数量
  }
  buf[i] = '\0';

  printf("\r\n&buf[1]: '%s'\r\n", &buf[1]);//打印调试信息，显示接收到的数据内容，忽略第一个字符
  if (buf[0] != '\x1b' || buf[1] != '[')
    return -1;//打印调试信息，显示接收到的数据内容，忽略第一个字符
  if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)//使用 sscanf() 从 buf 中解析行和列的信息，并将结果存储到指向的 rows 和 cols 指针中
    return -1;

  return 0;
}
//getWindowSize 函数用于获取当前终端窗口的大小（行数和列数）。
//它通过系统调用收集有关终端的信息，以便编辑器可以根据终端的大小调整内容的显示与布局
int getWindowSize(int *rows, int *cols) {
//struct winsize 是一个结构体，用于存储有关终端窗口大小的属性，包含行数和列数等
  struct winsize ws;
//通过系统调用获取终端窗口大小，并将其存储到ws变量中
  if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
//如果无法通过 ioctl 获取窗口大小，代码会发送一串控制序列 "\x1b[999C\x1b[999B" 到终端。
//这一序列尝试让光标移动到右下角，从而触发终端返回当前光标位置的大小（行列数）
    if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12)
      return -1;//ANSI序列 "\x1b[999C" 向右移动999个字符，"\x1b[999B" 向下移动999个字符。
    return getCursorPosition(rows, cols);//获取当前光标的坐标
  } else {
    *cols = ws.ws_col;//保存窗口的列数
    *rows = ws.ws_row;
    return 0;
  }
}

/*** syntax highlighting ***/
int is_separator(int c) {//判断字符是否是分隔符
  return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[];", c) != NULL;
}
//根据内容填充语法高亮信息
void editorUpdateSyntax(erow *row) {
  row->hl = realloc(row->hl, row->rsize);//重新分配内存，以便存储语法高亮信息
  memset(row->hl, HL_NORMAL, row->size);//初始化语法高亮信息为 HL_NORMAL，表示默认情况下不应用高亮

  if (E.syntax == NULL)
    return;

  char **keywords = E.syntax->keywords;//获取关键字数组

  char *scs = E.syntax->singleline_comment_start;//获取单行注释的起始符号
  int scs_len = scs ? strlen(scs) : 0;//获取单行注释的起始符号的长度

  char *mcs = E.syntax->multiline_comment_start;//获取多行注释的起始符号
  char *mce = E.syntax->multiline_comment_end; //获取多行注释的结束符号
  int mcs_len = mcs ? strlen(mcs) : 0;
  int mce_len = mce ? strlen(mce) : 0;

  int prev_sep = 1;//表示当前位置前一个字符是分隔符
  int in_string = 0;//表示当前不在字符串中
  int in_comment = (row->idx > 0 && E.row[row->idx - 1].hl_open_comment);//当前是否处于多行注释中
  int i = 0;
  while (i < row->rsize) {//遍历每一个字符
    char c = row->render[i];
    //获取前一个字符的高亮类别，若为第一个字符则默认使用 HL_NORMAL。
    unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL;
    if (scs_len && !in_string && !in_comment) {//不在字符串中，且不在多行注释中
      if (!strncmp(&row->render[i], scs, scs_len)) {
        //如果当前字符是单行注释的起始符号，则将其后面的所有字符的高亮类别设置为 HL_COMMENT，注释
        memset(&row->hl[i], HL_COMMENT, row->rsize - i);
        break;
      }
    }

    if (mcs_len && mce_len && !in_string) {
      if (in_comment) {
        row->hl[i] = HL_MLCOMMENT;//设置当前字符高亮类型为多行注释
        if (!strncmp(&row->render[i], mce, mce_len)) {// 检查是否遇到多行注释的结束符号
          memset(&row->hl[i], HL_MLCOMMENT, mce_len);// 使结束符号标记为多行注释
          i += mce_len;
          in_comment = 0;
          prev_sep = 1;//更新分隔符状态
          continue;
        } else {
          i++;
          continue;
        }
      } else if (!strncmp(&row->render[i], mcs, mcs_len)) {//匹配多行注释的起始符号
        memset(&row->hl[i], HL_MLCOMMENT, mcs_len);//设为多行注释高亮信息
        i += mcs_len;
        in_comment = 1;
        continue;
      }
    }

    if (E.syntax->flags & HL_HIGHLIGHT_STRINGS) {//判断是否启用高亮字符串
      if (in_string) {
        row->hl[i] = HL_STRING;//标记高亮信息为字符串
        if (c == '\\' && i + 1 < row->rsize) {
          row->hl[i + 1] = HL_STRING;
          i += 2;
          continue;
//当遇到反斜杠 ('\\') 时，设定下一个字符也为字符串高亮（因为反斜杠通常用于转义后面的字符），然后将索引 i 跳过这两个字符，继续下一个字符的处理
        }
        if (c == in_string)//如果当前字符是字符串的结束符号，则退出字符串状态
          in_string = 0;
        i++;
        prev_sep = 1;
        continue;
      } else {
        if (c == '"' || c == '\'') {//遇到这些符号，则进入字符串状态
          in_string = c;
          row->hl[i] = HL_STRING;//标记当前字符为字符串高亮
          i++;
          continue;
        }
      }
    }
    if (E.syntax->flags & HL_HIGHLIGHT_NUMBERS) {//判断是否启用高亮数字
      if (isdigit(c) && (prev_sep || prev_hl == HL_NUMBER) ||
          (c == '.' && prev_hl == HL_NUMBER)) {
        row->hl[i] = HL_NUMBER;
        i++;
        prev_sep = 0;
        continue;
      }
    }

    if (prev_sep) {//如果前一个字是分隔符，检查当前字符是否是关键字
      int j;
      for (j = 0; keywords[j]; j++) {//遍历关键字数组
        int klen = strlen(keywords[j]);
        int kw2 = keywords[j][klen - 1] == '|';// 检查关键字是否以 '|' 结尾
        if (kw2)
          klen--;
//kw2 是一个布尔变量，用于判断该关键字是否为第二类关键字（以 | 结尾）。
//如果是，则在匹配时需要减少长度，因为 | 表示该关键字应与后续标识符区分开
        if (!strncmp(&row->render[i], keywords[j], klen) &&
            is_separator(row->render[i + klen])) {
          memset(&row->hl[i], kw2 ? HL_KEYWORD2 : HL_KEYWORD1, klen);
          i += klen;
          break;
        }
      }
      if (keywords[j] != NULL) {
        prev_sep = 0;
        continue;
      }
    }
    prev_sep = is_separator(c);
    i++;
  }
  //表示当前行的多行注释状态是否发生变化
  int changed = (row->hl_open_comment != in_comment);
  row->hl_open_comment = in_comment;//更新多行注释状态
  if (changed && row->idx + 1 < E.numrows)
    editorUpdateSyntax(&E.row[row->idx+1]);//继续处理下一行的语法高亮
}

int editorSyntaxToColor(int hl) {
  switch (hl) {
  case HL_KEYWORD1:
    return 33;
  case HL_KEYWORD2:
    return 32;
  case HL_MLCOMMENT:
  case HL_COMMENT:
    return 36;
  case HL_STRING:
    return 35;
  case HL_NUMBER:
    return 31;
  case HL_MATCH:
    return 34;
  default:
    return 37;
  }
}

void editorSelectSyntaxHighlight() {
  E.syntax = NULL;//为空，表示当前没有应用语法高亮
  if (E.filename == NULL)
    return;
  char *ext = strchr(E.filename, '.');//获取文件扩展名

  for (unsigned int j = 0; j < HLDB_ENTRIES; j++) {//遍历 HLDB 数组
    struct editorSyntax *s = &HLDB[j];
    unsigned int i = 0;
    while (s->filematch[i]) {
      int is_ext = (s->filematch[i][0] == '.');//通过检查第一个字符是否是 '.' 来判断是否为扩展名匹配
      if ((is_ext && ext && !strcmp(ext, s->filematch[i])) ||
          (!is_ext && strstr(E.filename, s->filematch[i]))) {
        E.syntax = s;//设置高亮规则

        for (int filerow = 0; filerow < E.numrows; filerow++) {
          editorUpdateSyntax(&E.row[filerow]);
        }
        return;
      }
      i++;
    }
  }
}


//给定的字符索引 cx 转换为光标在终端显示中的实际横向位置 rx。
//这个转换考虑到了制表符的宽度，确保在渲染时光标的正确显示
int editorRowCxToRx(erow *row, int cx) {
  int rx = 0;
  int j;
  for (j = 0; j < cx; j++) {
    if (row->chars[j] == '\t') {
      rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP);
    }
    rx++;
  }
  return rx;
}

int editorRowRxToCx(erow *row, int rx) {
  int cur_rx = 0;
  int cx;
  for (cx = 0; cx < row->size; cx++) {
    if (row->chars[cx] == '\t')
      cur_rx += (KILO_TAB_STOP - 1) - (cur_rx % KILO_TAB_STOP);
    cur_rx++;
    if (cur_rx > rx)
      return cx;
  }
  return cx;
}
//更新编辑器中某一行的渲染内容
void editorUpdateRow(erow *row) {
  int tabs = 0;//统计制表符 ('\t') 的出现次数。这是为了在渲染时将制表符替换为适当数量的空格
  for (int j = 0; j < row->size; j++) {
    if (row->chars[j] == '\t')
      tabs++;
  }
  free(row->render);
  row->render = malloc(row->size + tabs * (KILO_TAB_STOP - 1) +
                       1); //分配新的渲染内容的内存

  int idx = 0;
  for (int j = 0; j < row->size; j++) {
    if (row->chars[j] == '\t') {
      row->render[idx++] = ' ';
      while (idx % KILO_TAB_STOP != 0)
        row->render[idx++] = ' ';
    } else {
      row->render[idx++] = row->chars[j];
    }
  }
  row->render[idx] = '\0';
  row->rsize = idx;

  editorUpdateSyntax(row);//更新语法高亮信息数组
}
/*int at：表示要插入行的索引位置。
char *s：指向要插入的字符串（文本），这个字符串的长度是 len。
size_t len：字符的实际长度*/
void editorInsertRow(int at, char *s, size_t len) {
  if (at < 0 || at > E.numrows)
    return;

  E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1));//重新分配内存
  //将at行及之后的行向下移动一行，给新插入的行留出空间
  memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));

  E.row[at].idx = at;//为新插入的行设置索引值
  for (int j = at + 1; j <= E.numrows; j++) {//将索引值后面的行的索引值都加1
    E.row[j].idx++;
  }

  E.row[at].size = len;
  E.row[at].chars = malloc(len + 1);
  memcpy(E.row[at].chars, s, len);
  E.row[at].chars[len] = '\0';

  E.row[at].rsize = 0;
  E.row[at].render = NULL;
  E.row[at].hl = NULL;
  E.row[at].hl_open_comment = 0;
  editorUpdateRow(&E.row[at]);//处理制表符，并根据内容更新语法高亮信息数组

  E.numrows++;
  E.dirty++;
}

void editorFreeRow(erow *row) {
  free(row->render);
  free(row->chars);
  free(row->hl);
}

void editorDelRow(int at) {
  if (at < 0 || at >= E.numrows)
    return;
  editorFreeRow(&E.row[at]);
  memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1));
  for (int j = at; j < E.numrows - 1; j++)
    E.row[j].idx--;
  E.numrows--;
  E.dirty++;
}

void editorRowInsertChar(erow *row, int at, int c) {
  if (at < 0 || at > row->size)
    at = row->size;
  row->chars = realloc(row->chars, row->size + 2);
  memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
  row->size++;
  row->chars[at] = c;
  editorUpdateRow(row);
  E.dirty++;
}

void editorInsertNewline() {
  if (E.cx == 0) {
    editorInsertRow(E.cy, "", 0);
  } else {
    erow *row = &E.row[E.cy];
    editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx);
    row = &E.row[E.cy];
    row->size = E.cx;
    row->chars[row->size] = '\0';
    editorUpdateRow(row);
  }
  E.cy++;
  E.cx = 0;
}

void editorRowAppendingString(erow *row, char *s, size_t len) {
  row->chars = realloc(row->chars, row->size + len + 1);
  memcpy(&row->chars[row->size], s, len);
  row->size += len;
  row->chars[row->size] = '\0';
  editorUpdateRow(row);
  E.dirty++;
}

void editorRowDelChar(erow *row, int at) {
  if (at < 0 || at >= row->size)
    return;
  memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
  row->size--;
  editorUpdateRow(row);
  E.dirty++;
}

/*** editor operations ***/

void editorInsertChar(int c) {
  if (E.cy == E.numrows) {
    editorInsertRow(E.numrows, "", 0);
  }
  editorRowInsertChar(&E.row[E.cy], E.cx, c);
  E.cx++;
}

void editorDelChar() {
  if (E.cy == E.numrows)
    return;
  if (E.cx == 0 && E.cy == 0)
    return;
  erow *row = &E.row[E.cy];
  if (E.cx > 0) {
    editorRowDelChar(row, E.cx - 1);
    E.cx--;
  } else {
    E.cx = E.row[E.cy - 1].size;
    editorRowAppendingString(&E.row[E.cy - 1], row->chars, row->size);
    editorDelRow(E.cy);
    E.cy--;
  }
}

/*** file i/o ***/

char *editorRowsToString(int *buflen) {
  int totlen = 0;
  int j;
  for (j = 0; j < E.numrows; j++) {
    totlen += E.row[j].size + 1;
  }
  *buflen = totlen;

  // ATTENTION: the caller should free() the memory
  char *buf = malloc(totlen);
  char *p = buf;
  for (j = 0; j < E.numrows; j++) {
    memcpy(p, E.row[j].chars, E.row[j].size);
    p += E.row[j].size;
    *p = '\n';
    p++;
  }
  return buf;
}
//打开指定文件并将其加载到编辑器中
void editorOpen(char *filename) {
  free(E.filename);
  E.filename = strdup(filename);//保存新文件名

  editorSelectSyntaxHighlight();//选择语法高亮规则

  FILE *fp = fopen(filename, "r");//以只读方式打开文件
  if (!fp)
    die("fopen");

  char *line = NULL;//用于保存从文件读取的每一行。
  size_t linecap = 0;//表示分配给 line 的缓冲区大小，初始为 0
  ssize_t linelen;//表示每行的字符长度。
   //c标准库中的函数，从文件指针fp中读取一行，并将其存储在line中，。
  while ((linelen = getline(&line, &linecap, fp)) != -1) {
    if (linelen != -1) {
      while (linelen > 0 &&
             (line[linelen - 1] == '\n' || line[linelen - 1] == '\r'))//使用 while 循环去掉行末的换行符（\n 或 \r），以确保插入到编辑器的行不包含换行字
        linelen--;
      editorInsertRow(E.numrows, line, linelen);
      //将读取的行插入到编辑器中
    }
  }
  free(line);
  fclose(fp);
  E.dirty = 0;
}

void editorSave() {
  if (E.filename == NULL) {
    E.filename = editorPrompt("Save as: %s", NULL);
    if (E.filename == NULL) {
      editorSetStatusMessage("Save aborted");
      return;
    }
    editorSelectSyntaxHighlight();
  }

  int len;
  char *buf = editorRowsToString(&len);
  int fd = open(E.filename, O_RDWR | O_CREAT, 0644);
  if (fd != -1) {
    if (ftruncate(fd, len) != -1) {
      if (write(fd, buf, len) == len) {
        close(fd);
        free(buf);
        E.dirty = 0;
        editorSetStatusMessage("%d bytes written to disk", len);
        return;
      }
      close(fd);
    }
  }
  free(buf);
  editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
}

/*** find ***/
void editorFindCallback(char *query, int key) {
  static int last_match = -1;
  static int direction = 1;

  static int saved_hl_line;
  static char *saved_hl = NULL;

  if (saved_hl) {
    memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize);
    free(saved_hl);
    saved_hl = NULL;
  }

  if (key == '\r' || key == '\x1b') {
    last_match = -1;
    direction = 1;
    return;
  } else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
    direction = 1;
  } else if (key == ARROW_LEFT || key == ARROW_UP) {
    direction = -1;
  } else {
    last_match = -1;
    direction = 1;
  }

  if (last_match == -1)
    direction = 1;
  int current = last_match;
  for (int i = 0; i < E.numrows; i++) {
    current += direction;
    if (current == -1)
      current = E.numrows - 1;
    else if (current == E.numrows)
      current = 0;

    erow *row = &E.row[current];
    char *match = strstr(row->render, query); // first match
    if (match) {
      last_match = current;
      E.cy = current;
      E.cx = editorRowRxToCx(row, match - row->render);
      // TRYING: don't put it to top for current matched line.
      /* E.rowoff = E.numrows; */

      saved_hl_line = current;
      saved_hl = malloc(row->rsize);
      memcpy(saved_hl, row->hl, row->rsize);
      memset(&row->hl[match - row->render], HL_MATCH, strlen(query));
      break;
    }
  }
}

void editorFind() {
  int saved_cx = E.cx;
  int saved_cy = E.cy;
  int saved_coloff = E.coloff;
  int saved_rowoff = E.rowoff;
  char *query =
      editorPrompt("Search: %s (Use ESC/Arrows/Enter)", editorFindCallback);
  if (query) {
    free(query);
  } else {
    E.cx = saved_cx;
    E.cy = saved_cy;
    E.coloff = saved_coloff;
    E.rowoff = saved_rowoff;
  }
}

/*** append buffer ***/

struct abuf {
  char *b;
  int len;
};

#define ABUF_INIT                                                              \
  { NULL, 0 }

void abAppend(struct abuf *ab, const char *s, int len) {
  char *new = realloc(ab->b, ab->len + len);

  if (new == NULL)
    return;
  memcpy(&new[ab->len], s, len);
  ab->b = new;
  ab->len += len;
}

void abFree(struct abuf *ab) { free(ab->b); }

/*** output ***/
//根据光标的位置，计算出屏幕上要显示的内容。
void editorScroll() {
  E.rx = 0;
  if (E.cy < E.numrows) {
    E.rx = editorRowCxToRx(&E.row[E.cy], E.cx);
    //根据字符索引cx计算出光标在屏幕上的横向位置rx（考虑到制表符的宽度）
  }

  if (E.cy < E.rowoff) {//根据光标的位置，计算出要显示的行何列的起始索引
    E.rowoff = E.cy;
  }
  if (E.cy >= E.rowoff + E.screenrows) {
    E.rowoff = E.cy - E.screenrows + 1;
  }
  if (E.rx < E.coloff) {
    E.coloff = E.rx;
  }
  if (E.rx >= E.coloff + E.screencols) {
    E.coloff = E.rx - E.screencols + 1;
  }
}
//将编辑器的内容渲染到屏幕上。
void editorDrawRows(struct abuf *ab) {
  int y;
  for (y = 0; y < E.screenrows; y++) {
    int filerow = y + E.rowoff;//计算出屏幕上要显示的行的索引值
    if (filerow >= E.numrows) {//检查是否有效
      if (E.numrows == 0 && y == E.screenrows / 3) {//如果编辑文件为空，则显示欢迎信息
        char welcome[80];
        int welcomelen = snprintf(welcome, sizeof(welcome),
                                  "Kilo editor -- version %s", KILO_VERSION);
        if (welcomelen > E.screencols)
          welcomelen = E.screencols;
        int padding = (E.screencols - welcomelen) / 2;
        if (padding) {
          abAppend(ab, "~", 1);
          padding--;
        }
        while (padding--)
          abAppend(ab, " ", 1);
        abAppend(ab, welcome, welcomelen);
      } else {
        abAppend(ab, "~", 1);
      }
    } else {//如果文件中有内容
      int len = E.row[filerow].rsize - E.coloff;//计算出屏幕上要显示的行的字符数
      if (len < 0)
        len = 0;
      if (len > E.screencols)
        len = E.screencols;
      char *c = &E.row[filerow].render[E.coloff];//获取要显示的行的字符
      unsigned char *hl = &E.row[filerow].hl[E.coloff];//获取最新的语法高亮信息数组
      int current_color = -1;
      for (int j = 0; j < len; j++) {
        if (iscntrl(c[j])) {//判断是否为控制字符，如换行，回车等
         /*为控制字符生成一个可视化的符号：
若控制字符的 ASCII 值小于或等于 27，使用  '@' + c[j] 进行转换，从而将控制字符转换为可显示的字符。
对于其他控制字符，使用 '? ' 作为代表符号。*/
          char sym = (c[j] <= 27) ? '@' + c[j] : '?';
          abAppend(ab, "\x1b[7m", 4);//反转字符的显示，将背景色与前景色互换
          abAppend(ab, &sym, 1);//输出字符
          abAppend(ab, "\x1b[m", 3);//恢复正常显示
          //同时按ctrl,可以强调当前字符。
          if (current_color != -1) {//设置颜色
            char buf[16];
            int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", current_color);
            abAppend(ab, buf, len);
          }
        }
        if (hl[j] == HL_NORMAL) {//不需要高亮
          if (current_color != -1) {
            abAppend(ab, "\x1b[39m", 5);// 恢复默认颜色
            current_color = -1;
          }
          abAppend(ab, &c[j], 1);
        } else {//需要高亮
          int color = editorSyntaxToColor(hl[j]);//根据高亮信息获取相应的颜色代码
          if (color != current_color) {
            current_color = color;
            char buf[16];
            int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", color);//设置颜色
            abAppend(ab, buf, clen);
          }
          abAppend(ab, &c[j], 1);
        }
      }
      abAppend(ab, "\x1b[39m", 5);//恢复默认颜色
    }

    abAppend(ab, "\x1b[K", 3);//清除当前行的剩余部分，使光标回到行首。

    abAppend(ab, "\r\n", 2);//输出换行符
  }
}
//绘制编辑状态栏信息
void editorDrawStatusBar(struct abuf *ab) {
  abAppend(ab, "\x1b[7m", 4);//用反转显示（将背景色与前景色互换）显示状态栏
  char status[80], rstatus[80];
  int len = snprintf(status, sizeof(status), "%.20s - %d lines %s",
                     E.filename ? E.filename : "[No Name]", E.numrows,
                     E.dirty ? "(modified)" : "");//生成状态栏信息字符串
  int rlen =
      snprintf(rstatus, sizeof(rstatus), "%s | %d/%d",
               E.syntax ? E.syntax->filetype : "no ft", E.cy + 1, E.numrows);
  if (len > E.screencols)
    len = E.screencols;//如果生成的状态信息超过当前屏幕列数，调整 len 变量以限制到屏幕可显示的宽度
  abAppend(ab, status, len);
  while (len < E.screencols) {//添加空格以使状态栏信息居中显示
    if (E.screencols - len == rlen) {
      abAppend(ab, rstatus, rlen);
      break;
    } else {
      abAppend(ab, " ", 1);
      len++;
    }
  }
  abAppend(ab, "\x1b[m", 3);//恢复默认属性
  abAppend(ab, "\r\n", 2);//输出换行符
}

void editorDrawMessageBar(struct abuf *ab) {
  abAppend(ab, "\x1b[K", 3);
  int msglen = strlen(E.statusmsg);
  if (msglen > E.screencols)
    msglen = E.screencols;
  if (msglen && time(NULL) - E.statusmsg_time < 5)
    abAppend(ab, E.statusmsg, msglen);
}

void editorRefreshScreen() {
  editorScroll();//根据光标的位置，计算出屏幕上要显示的内容
  struct abuf ab = ABUF_INIT;//初始化缓冲区
  abAppend(&ab, "\x1b[?25l", 6);//第一条控制序列 "\x1b[?25l" 是 ANSI 转义序列，表示隐藏光标。这使得在刷新屏幕时，不会看到光标的移动
  abAppend(&ab, "\x1b[H", 3);//第一条控制序列 "\x1b[?25l" 是 ANSI 转义序列，表示隐藏光标。这使得在刷新屏幕时，不会看到光标的移动

  editorDrawRows(&ab);
  editorDrawStatusBar(&ab);
  editorDrawMessageBar(&ab);

  char buf[32];
  snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1,
           (E.rx - E.coloff) + 1);
  abAppend(&ab, buf, strlen(buf));

  abAppend(&ab, "\x1b[?25h", 6);

  write(STDOUT_FILENO, ab.b, ab.len);
  abFree(&ab);
}
//显示状态栏信息
void editorSetStatusMessage(const char *fmt, ...) {
  va_list ap;//va_list是gcc提供的一种参数类型，它可以用来处理可变参数。
  va_start(ap, fmt);//使ap指向第一个可变参数。
  vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
  va_end(ap);
  E.statusmsg_time = time(NULL);
  /*vsnprintf：是一个函数，用于将格式化的数据写入字符串。具体参数如下：
E.statusmsg：目标字符串，此处是状态消息的存储位置。
sizeof(E.statusmsg)：目标字符串的最大长度，防止缓冲区溢出。
fmt：格式字符串，用于定义如何格式化后续参数。
ap：已初始化的可变参数列表。*/
}

/*** input ***/

char *editorPrompt(char *prompt, void (*callback)(char *, int)) {
  size_t bufsize = 128;
  char *buf = malloc(bufsize);

  size_t buflen = 0;
  buf[0] = '\0';

  while (1) {
    editorSetStatusMessage(prompt, buf);
    editorRefreshScreen();

    int c = editorReadKey();
    if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
      if (buflen != 0)
        buf[--buflen] = '\0';
    } else if (c == '\x1b') {
      editorSetStatusMessage("");
      if (callback)
        callback(buf, c);
      free(buf);
      return NULL;
    } else if (c == '\r') {
      if (buflen != 0) {
        editorSetStatusMessage("");
        if (callback)
          callback(buf, c);
        return buf;
      }
    } else if (!iscntrl(c) && c < 128) {
      if (buflen == bufsize - 1) {
        bufsize *= 2;
        buf = realloc(buf, bufsize);
      }
      buf[buflen++] = c;
      buf[buflen] = '\0';
    }
    if (callback)
      callback(buf, c);
  }
}

void editorMoveCursor(int key) {
  erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
  switch (key) {
  case ARROW_LEFT:
    if (E.cx != 0) {
      E.cx--;
    } else if (E.cy > 0) {
      E.cy--;
      E.cx = E.row[E.cy].size;
    }
    break;
  case ARROW_RIGHT:
    if (row && E.cx < row->size) {
      E.cx++;
    } else if (row && E.cx == row->size) {
      E.cy++;
      E.cx = 0;
    }
    break;
  case ARROW_UP:
    if (E.cy != 0) {
      E.cy--;
    }
    break;
  case ARROW_DOWN:
    if (E.cy < E.numrows) {
      E.cy++;
    }
    break;
  }

  row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
  int rowlen = row ? row->size : 0;
  if (E.cx > rowlen) {
    E.cx = rowlen;
  }
}

void editorProcessKeypress() {
  static int quit_times = KILO_QUIT_TIMES;

  int c = editorReadKey();

  switch (c) {
  case '\r': // Enter
    editorInsertNewline();
    break;
  case CTRL_KEY('q'):
    if (E.dirty && quit_times > 0) {
      editorSetStatusMessage("WARNING!!! File has unsaved changes. Press "
                             "Ctrl-Q %d more times to quit.",
                             quit_times);
      quit_times--;
      return;
    }
    write(STDOUT_FILENO, "\x1b[2J", 4);
    write(STDOUT_FILENO, "\x1b[H", 3);
    exit(0);
    break;
  case CTRL_KEY('s'):
    editorSave();
    break;
  case HOME_KEY:
    E.cx = 0;
    break;
  case END_KEY:
    if (E.cy < E.numrows)
      E.cx = E.row[E.cy].size;
    break;
  case CTRL_KEY('f'):
    editorFind();
    break;
  case BACKSPACE:
  case CTRL_KEY('h'):
  case DEL_KEY:
    if (c == DEL_KEY)
      editorMoveCursor(ARROW_RIGHT);
    editorDelChar();
    break;
  case PAGE_UP:
  case PAGE_DOWN: {
    if (c == PAGE_UP) {
      E.cy = E.rowoff;
    } else if (c == PAGE_DOWN) {
      E.cy = E.rowoff + E.screenrows - 1;
      if (E.cy > E.numrows)
        E.cy = E.numrows;
    }
    int times = E.screenrows;
    while (times--)
      editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
  } break;
  case ARROW_UP:
  case ARROW_DOWN:
  case ARROW_LEFT:
  case ARROW_RIGHT:
    editorMoveCursor(c);
    break;
  case CTRL_KEY('l'):
  case '\x1b':
    break;
  default:
    editorInsertChar(c);
    break;
  }

  quit_times = KILO_QUIT_TIMES;
}

/*** init ***/

void initEditor() {//初始化编辑器
  E.cx = 0;
  E.cy = 0;
  E.rx = 0;
  E.rowoff = 0;
  E.coloff = 0;
  E.numrows = 0;
  E.row = NULL;
  E.dirty = 0;
  E.filename = NULL;
  E.statusmsg[0] = '\0';
  E.statusmsg_time = 0;
  E.syntax = NULL;
  if (getWindowSize(&E.screenrows, &E.screencols) == -1)
    die("getWindowSize");
  E.screenrows -= 2;//E.screenrows 减去 2，因为一般终端屏幕底部会留出一行用于状态栏和另一行用于消息提示。
}

int main(int argc, char *argv[]) {
//int argc：命令行参数的数量（argument count）。它表示用户在命令行输入时提供的参数个数，包括程序本身的名称
//char *argv[]：一个字符串数组（字符指针数组），包含命令行输入的所有参数。argv[0] 通常是程序的名称，随后的元素是随程序名称一起提供的其他参数
  enableRawMode();//将终端设置为原始模式
  initEditor();//初始化

  if (argc >= 2) {
    editorOpen(argv[1]);//根据文件名打开文件
  }
  //设置状态栏信息
  editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find");

  while (1) {
    editorRefreshScreen();
    editorProcessKeypress();
  }
  return 0;
}
